package library.JpegNodes;

import java.io.File ;
import java.io.IOException;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.text.ParseException;

import org.apache.log4j.*;

import org.cmc.sanselan.Sanselan; // getMetadata
import org.cmc.sanselan.common.IImageMetadata;
import org.cmc.sanselan.formats.jpeg.JpegImageMetadata;
import org.cmc.sanselan.ImageReadException;
import org.cmc.sanselan.formats.tiff.TiffField;
import org.cmc.sanselan.formats.tiff.constants.TiffConstants;

import library.Configuration.*;

/**
 * Class_JpegFile defines the actions that can be performed on a jpeg file.<br/>
 * For the time being, only the renaming is implemented (not the filename
 * checking).
 * @author JNC
 */
@SuppressWarnings("serial")
public class JpegFile extends File {
    
  /**
     * The pattern for the filename of a photo.
     */
    public static final String _FILENAME_PATTERN_PHOTO = Configuration.getStringProperty("JpegFile.FilenamePatternPhoto");
    
    /**
     * The pattern for the filename of a scan.
     */
    public static final String _FILENAME_PATTERN_SCAN = Configuration.getStringProperty("JpegFile.FilenamePatternScan");
    
    /**
     * The pattern for the filename of a video.
     */
    public static final String _FILENAME_PATTERN_VIDEO = Configuration.getStringProperty("JpegFile.FilenamePatternVideo");
    
    /**
     * The jpeg extension.
     */
    public static final String _FILENAME_EXTENSION_JPEG = Configuration.getStringProperty("JpegFile.FilenameExtensionJpeg");;
    
    /**
     * The format to use to build a file name.
     */
    private static final String _FILENAME_FORMAT = Configuration.getStringProperty("JpegFile.FilenameFormat");;
    
    /**
     * The length of a date in a well-formed Jpeg file name.
     */   
    private static final int _DATE_LENGTH = 10 ;
    
    /**
     * The null date sometimes returned.
     */
    private static final String _NULL_DATE = "0000-00-00";

    /**
     * It appears that the standard API RenameTo sometimes fails : this number
     * is the maximum number of re-trials.
     */
    private static final int _RENAME_MAX_LOOP = 3;
    
    /**
     * It appears that the standard API RenameTo sometimes fails : this number 
     * is the wait duration in ms between consecutive re-trials.
     */
    private static final int _RENAME_WAIT = 1000; // ms
    
    /**
     * The log4j logger.
     */
    private static final Logger _logger = Logger.getLogger(JpegFile.class);
    
      /**
     * The Jpeg metadata as read in the Jpeg file.
     */
    private JpegImageMetadata _metaData;
    
    /**
     * The digitalization date as read in the metadata.
     */
    private Date _digitalizationDate;
    
    /**
     * The digitalization date as a string (only stored to save time as the 
     * digitalization date is already stored).
     */
    private String _digitalizationDateString;
    
    /**
     * The title as read in the metadata.
     */
    private String _title;
    
    /**
     * The new name of the file when renamed, but without absolute path.
     */
    private String _shortNewName;
    
    /**
     * The new name of the file when renamed, with absolute path.
     */
    private String _longNewName;
    
    
    /**
     * Constructor
     * @param mysFilePath
     * The file path of the Jpeg file
     * @throws org.cmc.sanselan.ImageReadException
     * Inherited from initialize()
     * @throws java.io.IOException
     * Inherited from initialize()
     */
    public JpegFile(String mysFilePath) throws ParseException, ImageReadException, IOException {
        super(mysFilePath);
        
        initialize();
        // throws exceptions in case of failure
    }
    
    /**
     * Constructor
     * @param myxFile
     * The Jpeg file itself
     * @throws org.cmc.sanselan.ImageReadException
     * Inherited from initialize()
     * @throws java.io.IOException
     * Inherited from initialize()
     */
    public JpegFile(File myxFile) throws ParseException, ImageReadException, IOException, NullPointerException {
        super(myxFile.getAbsolutePath());
        
        initialize();
        // throws exceptions in case of failure
    }
    
    /**
     * Returns the digitalization date attribute
     * @return
     * The digitalization date attribute
     */
    public String getDigitalizationDateString() {
        return _digitalizationDateString;
    }
    
    /**
     * Gets the digitalization date as embedded in well-formed Jpeg file name
     * @return
     * The digitalization date
     */
    public String getDigitalizationDateStringFromFilename() {
        return getName().substring(0,_DATE_LENGTH);
    }
    
    /**
     * Returns the title attribute
     * @return
     * The title attribute
     */
    public String getTitle() {
        return _title;
    }
    
    /**
     * Returns the new name of the file without absolute path
     * @return new name of the file without absolute path
     */
    public String shortNewName() {
        return _shortNewName;
    }
    
    /**
     * Returns the new name of the file with absolute path
     * @return new name of the file with absolute path
     */
    public String longNewName() {
        return _longNewName;
    }
    
    /**
     * If the Jpeg file embeds required information (title and digitalization date)
     * then builds the well-formed file name and renames the file
     */
    public void rename(int myIncrementedindex) {
        String newName = null;
        boolean renameSuccess = false;
        boolean abort = false;
        int iLoop;
        
        // check if all necessary data are available
        // -----------------------------------------
        if (!abort) {
            if (_digitalizationDate == null) {
                abort = true;
                _logger.error("Impossible to rename " + getAbsolutePath() + " : no digitalizationDate");
            } else {
                if (_title == null) {
                    abort = true;
                    _logger.error("Impossible to rename " + getAbsolutePath() + " : no title");
                } // if titre == null
            }// if _digitalizationDate != null
        } // if ! bAbort
               
        // build the new name
        // ------------------
        if (!abort) {
            // The separator char is different for Windows and Unix
            newName = getParent()
                    + File.separatorChar
                    + String.format(_FILENAME_FORMAT,
                                     _digitalizationDateString,
                                     myIncrementedindex,
                                     _title);
        }
        
        // test if the file name does not already exists
        // ---------------------------------------------
        if (!abort) {
            if (new File(newName).exists()) {
                _logger.error("Impossible to rename " + getAbsolutePath() + " : filename " + newName + " exists");
                abort = true;
            }
        }
        
        // finally rename the file
        // ------------------------
        if (!abort) {
            
            for (iLoop = 1; ((iLoop<=_RENAME_MAX_LOOP) && (!renameSuccess)); iLoop++) {
                
                renameSuccess = renameTo(new File(newName));
                
                // It appears the RenameTo sometimes falls into error
                // give RenameTo some more chances in case of failure
                if (!renameSuccess) {
                    _logger.error("Renaming file " + getAbsolutePath() + " to " + newName + " failed");
                
                    // wait some time
                    try {
                        Thread.sleep(_RENAME_WAIT);
                    }
                    catch (InterruptedException exception) {
                        _logger.error("InterruptedException caught while waiting : " + exception);
                    }   
                }
            }
            
            // test for success and failure
            if (renameSuccess && (iLoop > 2)) {
                _logger.warn("File " + getAbsolutePath() + " finally renamed after " + iLoop + " trials");
            } else {
                if ( (!renameSuccess) && (iLoop == _RENAME_MAX_LOOP+1)) {
                    _logger.error("Impossible to rename " + getAbsolutePath() + " : renameTo failed after " + _RENAME_MAX_LOOP + " trials");
                    abort = true;
                }
            }              
        } // if !bAbort
        
        // set the short and long new names
        if (!abort) {
            _longNewName = newName;
            _shortNewName = newName.substring(newName.lastIndexOf(java.io.File.separatorChar)+1);
        }
    }

    /**
     * Checks the file name of the Jpeg file against information found in the metadata.
     * Not yet implemented.
     */
    public void check() {
        _logger.error("Not yet implemented");        
    }

    /**
     * Gets the metadata from the Jpeg file, and extracts the title and the digitalization date
     * @throws org.cmc.sanselan.ImageReadException
     * @throws java.io.IOException
     */
    private void initialize() throws ImageReadException, IOException, ParseException, NullPointerException {
        // set the metadata
        // throws exceptions in case of failure
        _metaData = getMetaDataFromExif();
        
        // set the title from exif data
        // throws exceptions in case of failure
        _title = getTitleFromExif();
        
        // set the digitalization dates from exif data
        // throws exceptions in case of failure
        updateDigitalizationDatesFromExif();
    }

    /**
     * Gets the metadata from the Jpeg file
     * @return
     * The metadata from the Jpeg file
     * @throws org.cmc.sanselan.ImageReadException
     * @throws java.io.IOException
     */
    private JpegImageMetadata getMetaDataFromExif() throws ImageReadException, IOException {
        IImageMetadata xMetadata;
        
        // get the metadata from Exif
        xMetadata = Sanselan.getMetadata(this);
        
        if (xMetadata instanceof JpegImageMetadata) {
            return (JpegImageMetadata)xMetadata;
        } else {
            _logger.error("File " + this.getName() + " returned metadata which is not an instance of JpegImageMetadata");
            throw new ImageReadException("File " + this.getName() + " returned metadata which is not an instance of JpegImageMetadata");
        }
    }
    
    /**
     * Gets the title from the metadata
     * @return
     * The title from the metadata
     */
    private String getTitleFromExif() {
        TiffField xField = null;
        String sReturn = null;
        boolean bAbort = false;
        StringBuffer sBuffer = null;  
        
        // get the tag
        if (! bAbort) {
            xField = _metaData.findEXIFValue(TiffConstants.EXIF_TAG_XPTITLE);
            if (xField == null) {
                _logger.warn("No title in Exif data for " + getName());
                bAbort = true;
            } // if xField == null
        } // if ! bAbort
        
        // get the value
        if (! bAbort) {
            sBuffer = new StringBuffer("");

            for (int iLoop = 0 ; iLoop < xField.oversizeValue.length / 2; iLoop ++) {
                
                switch(xField.oversizeValue[iLoop*2])
                {
                    case (byte)0x00 :
                        break;
                    case (byte)0x8C :
                        sBuffer.append("OE");
                        break;
                    case (byte)0x9C :
                        sBuffer.append("oe");
                        break;
                    case (byte)0x9F :
                        sBuffer.append("Y"); // Ÿ
                        break;
                    case (byte)0xC7 :
                        sBuffer.append("C"); // Ç
                        break;
                    case (byte)0xE0 :
                        sBuffer.append("a"); // à
                        break;
                    case (byte)0xE1 :
                        sBuffer.append("a"); // á
                        break;
                    case (byte)0xE2 : 
                        sBuffer.append("a"); // â
                        break;
                    case (byte)0xE3 :
                        sBuffer.append("a"); // ã
                        break;
                    case (byte)0xE4 :
                        sBuffer.append("a"); // ä
                        break;
                    case (byte)0xE5 :
                        sBuffer.append("a"); // å
                        break;
                    case (byte)0xE6 :
                        sBuffer.append("ae"); // æ
                        break;
                    case (byte)0xE7 :
                        sBuffer.append("c"); // ç
                        break;
                    case (byte)0xE8 : 
                        sBuffer.append("e"); // è
                        break;
                    case (byte)0xE9 : 
                        sBuffer.append("e"); // é
                        break;
                    case (byte)0xEA : 
                        sBuffer.append("e"); // ê
                        break;
                    case (byte)0xEB : 
                        sBuffer.append("e"); // ë
                        break;
                    case (byte)0xEF : 
                        sBuffer.append("i"); // ï
                        break;
                    case (byte)0xFB : 
                        sBuffer.append("u"); // û
                        break;                            
                    case (byte)0xFC : 
                        sBuffer.append("u"); // ü
                        break;
                    case (byte)0xF9 : 
                        sBuffer.append("u"); // ù
                        break;
                    case (byte)0xFF :
                        sBuffer.append("y"); // ÿ
                        break;
                        
                    default : 
                        sBuffer.append((char)xField.oversizeValue[iLoop*2]);
                        break;
                }
            } // for
            sReturn = sBuffer.toString();
        }
        return sReturn;
    }        
    
    /**
     * Gets the digitalization dates from the metadata.
     * Update both attributes _digitalizationDate and _digitalizationDateString
     * reading metadata from the file.
     * @throws java.text.ParseException
     * @throws org.cmc.sanselan.ImageReadException
     * @throws java.lang.NullPointerException
     */
    private void updateDigitalizationDatesFromExif() throws ParseException, ImageReadException, NullPointerException {
        
        TiffField field = null;
        String exifValue = null;
                
        // read the tag
        // ------------
        field = _metaData.findEXIFValue(TiffConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
        
        if (field != null) {
            exifValue = field.getStringValue();
            // throws ImageReadException in case of failure
        } else {
            String errMsg = "findEXIFValue returned null for " + getName();
            _logger.error(errMsg);
            throw new NullPointerException(errMsg);
        }
        
        // converts the EXIF tag to date
        // ------------------------------
        _digitalizationDate = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(exifValue);
        // throws ParseException and then aborts in case of failure    
       
        // converts the EXIF tag to string
        // -------------------------------
        _digitalizationDateString = exifValue.substring(0, _DATE_LENGTH).replace(':', '-') ;
        
        if (_digitalizationDateString.equals(_NULL_DATE)) {
            String errMsg = "findEXIFValue returned : " + _digitalizationDate;
            _logger.error(errMsg);
            throw new NullPointerException(errMsg);
        }
    } 
}